Skip to content

feat: Expand eye filter into multi-choice visibility filters - #254

Merged
richardthe3rd merged 8 commits into
mainfrom
copilot/update-eye-filter-options
May 11, 2026
Merged

feat: Expand eye filter into multi-choice visibility filters#254
richardthe3rd merged 8 commits into
mainfrom
copilot/update-eye-filter-options

Conversation

Copilot AI commented May 11, 2026

Copy link
Copy Markdown
Contributor

The single availability toggle ("eye" button) is replaced with a multi-choice filter sheet, giving users independent control over visibility and dietary conditions.

New filters

  • Available only – hides sold out / not yet arrived drinks
  • Not tasted – hides drinks already marked as tasted
  • Vegan only – shows only drinks with is_vegan: true
  • Per-allergen exclusions – dynamic list of allergens drawn from loaded drinks data; each allergen gets its own checkbox (e.g. Gluten, Sulphites); a drink passes if the allergen value is 0 or absent; multiple selections are ANDed

Domain / model changes

  • Product.isVegan — nullable bool, parsed robustly from is_vegan / vegan JSON fields (handles bool, num, and String values)
  • Product.isAllergenFree — computed: allergens empty or all values 0
  • DrinkVisibilityFilter enum added to lib/domain/models/ (availableOnly, notTasted, veganOnly)
  • DrinkFilterService.filterDrinks signature changed: bool hideUnavailableSet<DrinkVisibilityFilter> visibilityFilters + Set<String> excludedAllergens
  • filterByNotTasted, filterByVegan, filterByExcludedAllergens methods added to DrinkFilterService

Provider

  • _hideUnavailable: bool replaced with _visibilityFilters: Set<DrinkVisibilityFilter>
  • hideUnavailable getter preserved as visibilityFilters.contains(availableOnly) for backward compat
  • setVisibilityFilter(filter, active) and clearVisibilityFilters() added; setHideUnavailable becomes a thin wrapper
  • _excludedAllergens: Set<String> added with setAllergenFilter(allergen, active) and clearAllergenFilters()
  • availableAllergens getter — derived live from loaded drinks, drives the allergen section of the sheet
  • All filters persisted: visibilityFilters (string list), excludedAllergens (string list); migrates legacy hideUnavailable bool on first run

UI

  • _AvailabilityButton (toggle) → _VisibilityFilterButton: eye icon with a numeric badge (visibilityFilters.length + excludedAllergens.length) when any filter is active, opens _VisibilityFilterSheet
  • Sheet shows three fixed CheckboxListTile rows (available only, not tasted, vegan only), then a labelled "Allergen-free" section with alphabetically-sorted per-allergen rows drawn from availableAllergens
  • Clear button resets all visibility and allergen filters in a single tap

Tests

New/updated coverage for:

  • isVegan model parsing (true/false/null/numeric/string variants)
  • isAllergenFree computed property
  • filterByNotTasted, filterByVegan, filterByExcludedAllergens service methods (including value-0 and missing-key pass-through cases, AND logic for multiple exclusions)
  • All visibility filter paths in BeerProvider including persistence, legacy key migration, and allergen filter persistence/restore

Copilot AI and others added 5 commits May 11, 2026 09:23
- Add DrinkVisibilityFilter enum (availableOnly, notTasted, veganOnly, allergenFree)
- Add isVegan field to Product (parses is_vegan from JSON)
- Add isAllergenFree computed property to Product/Drink
- Update DrinkFilterService: new filterByNotTasted/Vegan/AllergenFree methods,
  filterDrinks now takes Set<DrinkVisibilityFilter> instead of bool hideUnavailable
- Update BeerProvider: replace _hideUnavailable with _visibilityFilters set,
  add setVisibilityFilter, keep setHideUnavailable/hideUnavailable for compat,
  persist all filters under visibilityFilters prefs key, migrate legacy key
- Replace _AvailabilityButton toggle with _VisibilityFilterButton that opens
  _VisibilityFilterSheet with 4 checkboxes and active-filter badge
- Add tests: isVegan/isAllergenFree model tests, new filter service tests,
  new beer provider tests for all 4 visibility filters

Agent-Logs-Url: https://github.com/richardthe3rd/cambridge-beer-festival-app/sessions/2e71429f-96a9-46bb-94cc-6f940fce1975

Co-authored-by: richardthe3rd <573334+richardthe3rd@users.noreply.github.com>
…t legacy pref key not written

Agent-Logs-Url: https://github.com/richardthe3rd/cambridge-beer-festival-app/sessions/2e71429f-96a9-46bb-94cc-6f940fce1975

Co-authored-by: richardthe3rd <573334+richardthe3rd@users.noreply.github.com>
…ar-all efficiency

- Add missing `});` closing the visibility filters test group (build was broken)
- Remove unused `provider` field from `_VisibilityFilterSheet` (Consumer handles rebuilds)
- Add `clearVisibilityFilters()` to BeerProvider for a single atomic prefs write
- Update Clear button to call `clearVisibilityFilters()` instead of N sequential writes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add `dense: true` and a `Semantics` wrapper (label/value/selected/button)
to `_VisibilityFilterTile` to match the pattern used by `_StyleFilterSheet`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keep isVegan field name from this PR but preserve the robust multi-type
parsing (bool/num/string) introduced by #252. Remove the duplicate fragile
cast and rename all .vegan accessor references to .isVegan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@richardthe3rd
richardthe3rd force-pushed the copilot/update-eye-filter-options branch from c96f8a3 to 1be2eb1 Compare May 11, 2026 08:26
@richardthe3rd richardthe3rd changed the title Expand eye filter into multi-choice visibility filters feat: Expand eye filter into multi-choice visibility filters May 11, 2026
richardthe3rd and others added 2 commits May 11, 2026 09:43
Replace the single 'allergen-free' checkbox with a dynamic list of per-
allergen filters derived from the loaded drinks data. Each allergen (e.g.
gluten, sulphites) gets its own checkbox; a drink passes if the allergen
value is 0 or absent. Multiple selections are ANDed.

- Remove allergenFree from DrinkVisibilityFilter enum
- Add filterByExcludedAllergens() to DrinkFilterService
- Add excludedAllergens param to filterDrinks()
- Add excludedAllergens, availableAllergens, setAllergenFilter(),
  clearAllergenFilters() to BeerProvider with prefs persistence
- Sheet shows a labelled allergen section with sorted per-allergen tiles
- Badge count includes both visibility and allergen filter counts
- Clear button clears both visibility and allergen filters

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
`json['allergens'] as Map<String, dynamic>?` throws at runtime when the
value is a `Map<dynamic, dynamic>` literal (e.g. `{}` in tests, or from
a JSON decoder that hasn't been typed). Switch to an `is Map` guard which
accepts any map type and still lets the per-entry parsing handle values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

LCOV of commit cac57d8 during CI #144

Summary coverage rate:
  lines......: 77.0% (2420 of 3142 lines)
  functions..: no data found
  branches...: no data found

Files changed coverage rate:
                                                      |Lines       |Functions  |Branches    
  Filename                                            |Rate     Num|Rate    Num|Rate     Num
  ==========================================================================================
  lib/domain/services/drink_filter_service.dart       | 0.0%     51|    -     0|    -      0
  lib/models/drink.dart                               | 0.0%    119|    -     0|    -      0
  lib/providers/beer_provider.dart                    | 0.0%    259|    -     0|    -      0
  lib/screens/drink_detail_screen.dart                | 0.0%    185|    -     0|    -      0
  lib/screens/drinks_screen.dart                      | 0.0%    259|    -     0|    -      0

@codecov

codecov Bot commented May 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 49.03226% with 79 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
lib/screens/drinks_screen.dart 9.19% 79 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Cloudflare Pages Preview

Your preview deployment is ready!

Preview URL: https://copilot-update-eye-filter-op.staging-cambeerfestival.pages.dev

This preview will be automatically updated when you push new commits to this PR.

…nk.isAllergenFree

Adds three tests to close the coverage gaps flagged by Codecov on this PR:
- setAllergenFilter with active=false (remove branch)
- clearVisibilityFilters() method
- Drink.isAllergenFree delegate getter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Cloudflare Pages Preview

Your preview deployment is ready!

Preview URL: https://copilot-update-eye-filter-op.staging-cambeerfestival.pages.dev

This preview will be automatically updated when you push new commits to this PR.

@richardthe3rd
richardthe3rd marked this pull request as ready for review May 11, 2026 15:47
Copilot AI review requested due to automatic review settings May 11, 2026 15:47
@richardthe3rd
richardthe3rd merged commit 54f3bf9 into main May 11, 2026
12 of 13 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands the drinks list “eye” toggle into a multi-choice visibility/dietary filtering system, enabling independent filters (availability, not tasted, vegan-only, and per-allergen exclusions) with persistence in SharedPreferences.

Changes:

  • Add vegan/allergen-related model fields (Product.isVegan, Product.isAllergenFree) and propagate them through Drink.
  • Introduce DrinkVisibilityFilter and extend DrinkFilterService + BeerProvider to support multiple visibility filters and excluded allergens (including persistence and legacy handling).
  • Replace the single availability button with a filter button + bottom sheet UI, and update/extend test coverage.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
lib/models/drink.dart Replaces vegan with isVegan, adds isAllergenFree, and updates JSON parsing/serialization.
lib/domain/models/models.dart Exports the new visibility-filter enum.
lib/domain/models/drink_visibility_filter.dart Adds DrinkVisibilityFilter enum for multi-toggle visibility filters.
lib/domain/services/drink_filter_service.dart Updates filtering API to accept visibilityFilters + excludedAllergens and adds new filter helpers.
lib/providers/beer_provider.dart Replaces _hideUnavailable with _visibilityFilters, adds _excludedAllergens and availableAllergens, and persists filter state.
lib/screens/drinks_screen.dart Replaces the availability toggle with a filter button (badge) and adds a visibility/allergen filter bottom sheet.
lib/screens/drink_detail_screen.dart Updates vegan indicator to use drink.isVegan.
test/models_test.dart Updates existing vegan assertions and adds coverage for isVegan parsing + isAllergenFree.
test/domain/services/drink_filter_service_test.dart Adds coverage for not-tasted/vegan/allergen-exclusion filtering and updates filterDrinks calls.
test/beer_provider_test.dart Adds coverage for new visibility/allergen filter persistence and legacy preference behavior.
test/drink_detail_screen_test.dart Updates test product construction to use isVegan.

Comment on lines +139 to +140
Set<DrinkVisibilityFilter> visibilityFilters = const {},
Set<String> excludedAllergens = const {},
Comment on lines +203 to +218
// Load visibility filter preferences (with migration from legacy hideUnavailable key)
_visibilityFilters = {};
final savedFilters = prefs.getStringList('visibilityFilters');
if (savedFilters != null) {
for (final name in savedFilters) {
final filter = DrinkVisibilityFilter.values
.where((f) => f.name == name)
.firstOrNull;
if (filter != null) _visibilityFilters.add(filter);
}
} else {
// Migrate from legacy 'hideUnavailable' boolean preference
if (prefs.getBool('hideUnavailable') ?? false) {
_visibilityFilters.add(DrinkVisibilityFilter.availableOnly);
}
}
Comment on lines +517 to +523
void _showVisibilityFilter(BuildContext context, BeerProvider provider) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => const _VisibilityFilterSheet(),
);
}
Comment on lines +673 to +689
child: Container(
width: 14,
height: 14,
decoration: BoxDecoration(
color: theme.colorScheme.primary,
shape: BoxShape.circle,
),
child: Center(
child: Text(
'$activeCount',
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: 9,
fontWeight: FontWeight.bold,
),
),
),
Set<String> get availableAllergens {
final allergens = <String>{};
for (final drink in _allDrinks) {
allergens.addAll(drink.allergens.keys);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants